package lock

import (
	"sort"
	"sync"
)

type Locks struct {
	tables []*sync.RWMutex
}

func Make(tableSize int) *Locks {
	tables := make([]*sync.RWMutex, tableSize)
	for i := 0; i < tableSize; i++ {
		tables[i] = &sync.RWMutex{}
	}
	return &Locks{ tables: tables }
}

const prime32 = uint32(16777619)

// fnv
func fnv32(key string) uint32  {
	hash := uint32(2166136261)
	for i := 0; i < len(key);i++ {
		hash *= prime32
		hash ^= uint32(key[i])
	}
	return hash
}
func (locks *Locks) spread(hashCode uint32) uint32 {
	if locks == nil {
		panic("locks is nil")
	}
	n := uint32(len(locks.tables))
	return (n - 1) & hashCode
}

func (locks *Locks) Lock(key string) {
	index := locks.spread(fnv32(key))
	mu := locks.tables[index]
	mu.Lock()
}
func (locks *Locks) RLock(key string) {
	index := locks.spread(fnv32(key))
	mu := locks.tables[index]
	mu.RLock()
}
func (locks *Locks) UnLock(key string) {
	index := locks.spread(fnv32(key))
	mu := locks.tables[index]
	mu.Unlock()
}
func (locks *Locks) RUnLock(key string) {
	index := locks.spread(fnv32(key))
	mu := locks.tables[index]
	mu.RUnlock()
}

// lock keys shard
func (locks *Locks) toLockIndices(keys []string, reverse bool) []uint32 {
	indexMap := make(map[uint32]bool)
	for _, key := range keys {
		index := locks.spread(fnv32(key))
		indexMap[index] = true
	}
	indices := make([]uint32, 0, len(indexMap))
	for index := range indexMap {
		indices = append(indices, index)
	}
	sort.Slice(indices, func(i, j int) bool {
		if reverse {return indices[i] > indices[j]}
		return indices[i] < indices[j]
	})
	return indices
}

func (locks *Locks) Locks(key ...string) {
	indices := locks.toLockIndices(key, false)
	for _, index := range indices {
		locks.tables[index].Lock()
	}
}
func (locks *Locks) RLocks(key ...string) {
	indices := locks.toLockIndices(key, false)
	for _, index := range indices {
		locks.tables[index].RLock()
	}
}

func (locks *Locks) UnLocks(key ...string) {
	indices := locks.toLockIndices(key, false)
	for _, index := range indices {
		locks.tables[index].Unlock()
	}
}
func (locks *Locks) RUnLocks(key ...string) {
	indices := locks.toLockIndices(key, false)
	for _, index := range indices {
		locks.tables[index].RUnlock()
	}
}

// wkey Lock   rKey RLock
func (locks *Locks) RWLocks(writeKeys []string, readKeys []string) {
	unionKeys := append(writeKeys, readKeys...)
	indices := locks.toLockIndices(unionKeys, false)
	writeIndices := locks.toLockIndices(writeKeys, false)
	writeIndexSet := make(map[uint32]struct{})
	for _, index := range writeIndices {
		writeIndexSet[index] = struct{}{}
	}
	for _, index := range indices {
		_, w := writeIndexSet[index]
		mu := locks.tables[index]
		if w {
			mu.Lock()
		}else {
			mu.RLock()
		}
	}
}

func (locks *Locks) RWUnLocks(writeKeys []string, readKeys []string) {
	unionKeys := append(writeKeys, readKeys...)
	indices := locks.toLockIndices(unionKeys, false)
	writeIndices := locks.toLockIndices(writeKeys, false)
	writeIndexSet := make(map[uint32]struct{})
	for _, index := range writeIndices {
		writeIndexSet[index] = struct{}{}
	}
	for _, index := range indices {
		_, w := writeIndexSet[index]
		mu := locks.tables[index]
		if w {
			mu.Unlock()
		}else {
			mu.RUnlock()
		}
	}
}




